﻿using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows.Forms;

internal class OpenFolderDialog : IDisposable
{

	/// <summary>
	/// Gets/sets folder in which dialog will be open.
	/// </summary>
	public string InitialFolder { get; set; }

	/// <summary>
	/// Gets/sets directory in which dialog will be open if there is no recent directory available.
	/// </summary>
	public string DefaultFolder { get; set; }

	/// <summary>
	/// Gets selected folder.
	/// </summary>
	public string Folder { get; private set; }


	internal DialogResult ShowDialog(IntPtr handle)
	{
		if (Environment.OSVersion.Version.Major >= 6)
		{
			return ShowVistaDialog(handle);
		}
		else
		{
			return ShowLegacyDialog(handle);
		}
	}

	private DialogResult ShowVistaDialog(IntPtr handle)
	{
		var frm = (NativeMethods.IFileDialog)(new NativeMethods.FileOpenDialogRCW());
		uint options;
		frm.GetOptions(out options);
		options |= NativeMethods.FOS_PICKFOLDERS | NativeMethods.FOS_FORCEFILESYSTEM | NativeMethods.FOS_NOVALIDATE | NativeMethods.FOS_NOTESTFILECREATE | NativeMethods.FOS_DONTADDTORECENT;
		frm.SetOptions(options);
		if (this.InitialFolder != null)
		{
			NativeMethods.IShellItem directoryShellItem;
			var riid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"); //IShellItem
			if (NativeMethods.SHCreateItemFromParsingName(this.InitialFolder, IntPtr.Zero, ref riid, out directoryShellItem) == NativeMethods.S_OK)
			{
				frm.SetFolder(directoryShellItem);
			}
		}
		if (this.DefaultFolder != null)
		{
			NativeMethods.IShellItem directoryShellItem;
			var riid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"); //IShellItem
			if (NativeMethods.SHCreateItemFromParsingName(this.DefaultFolder, IntPtr.Zero, ref riid, out directoryShellItem) == NativeMethods.S_OK)
			{
				frm.SetDefaultFolder(directoryShellItem);
			}
		}

		if (frm.Show(handle) == NativeMethods.S_OK)
		{
			NativeMethods.IShellItem shellItem;
			if (frm.GetResult(out shellItem) == NativeMethods.S_OK)
			{
				IntPtr pszString;
				if (shellItem.GetDisplayName(NativeMethods.SIGDN_FILESYSPATH, out pszString) == NativeMethods.S_OK)
				{
					if (pszString != IntPtr.Zero)
					{
						try
						{
							this.Folder = Marshal.PtrToStringAuto(pszString);
							return DialogResult.OK;
						}
						finally
						{
							Marshal.FreeCoTaskMem(pszString);
						}
					}
				}
			}
		}
		return DialogResult.Cancel;
	}

	private DialogResult ShowLegacyDialog(IntPtr handle)
	{
		using (var frm = new SaveFileDialog())
		{
			frm.CheckFileExists = false;
			frm.CheckPathExists = true;
			frm.CreatePrompt = false;
			frm.Filter = "|" + Guid.Empty.ToString();
			frm.FileName = "any";
			if (this.InitialFolder != null) { frm.InitialDirectory = this.InitialFolder; }
			frm.OverwritePrompt = false;
			frm.Title = "Select Folder";
			frm.ValidateNames = false;
			if (frm.ShowDialog() == DialogResult.OK)
			{
				this.Folder = Path.GetDirectoryName(frm.FileName);
				return DialogResult.OK;
			}
			else
			{
				return DialogResult.Cancel;
			}
		}
	}


	public void Dispose() { } //just to have possibility of Using statement.

}

internal static class NativeMethods
{

	#region Constants

	public const uint FOS_PICKFOLDERS = 0x00000020;
	public const uint FOS_FORCEFILESYSTEM = 0x00000040;
	public const uint FOS_NOVALIDATE = 0x00000100;
	public const uint FOS_NOTESTFILECREATE = 0x00010000;
	public const uint FOS_DONTADDTORECENT = 0x02000000;

	public const uint S_OK = 0x0000;

	public const uint SIGDN_FILESYSPATH = 0x80058000;

	#endregion


	#region COM

	[ComImport, ClassInterface(ClassInterfaceType.None), TypeLibType(TypeLibTypeFlags.FCanCreate), Guid("DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7")]
	internal class FileOpenDialogRCW { }


	[ComImport(), Guid("42F85136-DB7E-439C-85F1-E4075D135FC8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
	internal interface IFileDialog
	{
		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		[PreserveSig()]
		uint Show([In, Optional] IntPtr hwndOwner); //IModalWindow 


		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint SetFileTypes([In] uint cFileTypes, [In, MarshalAs(UnmanagedType.LPArray)] IntPtr rgFilterSpec);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint SetFileTypeIndex([In] uint iFileType);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint GetFileTypeIndex(out uint piFileType);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint Advise([In, MarshalAs(UnmanagedType.Interface)] IntPtr pfde, out uint pdwCookie);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint Unadvise([In] uint dwCookie);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint SetOptions([In] uint fos);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint GetOptions(out uint fos);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		void SetDefaultFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint AddPlace([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, uint fdap);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint Close([MarshalAs(UnmanagedType.Error)] uint hr);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint SetClientGuid([In] ref Guid guid);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint ClearClientData();

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter);
	}


	[ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
	internal interface IShellItem
	{
		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint BindToHandler([In] IntPtr pbc, [In] ref Guid rbhid, [In] ref Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out IntPtr ppvOut);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint GetParent([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint GetDisplayName([In] uint sigdnName, out IntPtr ppszName);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint GetAttributes([In] uint sfgaoMask, out uint psfgaoAttribs);

		[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
		uint Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder);
	}

	#endregion


	[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
	internal static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IntPtr pbc, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellItem ppv);

}